package com.ailot.cloud.base.security.feign;

import com.ailot.cloud.base.common.constants.SecurityConstant;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;

import java.util.Optional;


/**
 * 请求传递
 * <p>
 * 如果是内部服务的请求
 * 需要标记传递 Token 信息 其他不传递
 * 简化传递的信息
 * <p>
 * 或者强制手动标记需要传递 token
 * <p>
 * 对外部的请求不需要传递token信息
 * 否则会导致token泄露盗用等问题
 * <p>
 * 判断是否是内部请求 从头部信息中获取
 * <p>
 * <p>
 * 注意配置文件中的默认 头部信息 分别代表了
 * 标识内部请求 主要用于权限部分处理
 * X-FROM-INTERNAL-XXX: WOW
 * 标识是feign请求 主要用于是否拆包Result对象
 * X-From-Feign-Yo: Ready
 * <p>
 * 官方有个 OAuth2AccessTokenInterceptor 传递
 * <p>
 * TODO 如何标记不需要传递 token 需要一个feign的示例
 * <p>
 * X-FROM-INTERNAL-XXX
 * 网关 ->/-> 服务一 Feign/X-FROM-INTERNAL-XXX -> 服务二
 */
@Slf4j
public class AuthTokenPassInterceptor implements RequestInterceptor {

    /**
     * fein拦截器将本服务的token,通过copyToken的形式传递给下游服务
     *
     * @param requestTemplate 请求调用模板类
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        // 请求传递 token
        requestTemplate.header(SecurityConstant.AUTHORIZATION, getToken());
    }

    /**
     * 获取 token 不从请求中获取
     * 引入如果是跨线程的调用 是没有token 可以获取的
     * 常规应该是从 SecurityContextHolder 去获取
     * <p>
     * 跨线程的会手动去处理
     */
    protected String getToken() {
        return Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
                // 限定了 token认证对象 类型
                .map(authentication -> authentication instanceof JwtAuthenticationToken ? authentication : null)
                .map(authentication -> ((JwtAuthenticationToken) authentication).getToken().getTokenValue()).orElse(null);
    }

}
